LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

UnityShader入门精要①(渲染流水线)

2021/8/26

本文为学习UnityShader时所记录的笔记,供学习产出和日后复习使用。

学习资料为冯乐乐(问就是我女神)的《UnityShader入门精要》

2.1 综述

什么是渲染流水线

渲染流水线分为三个阶段,应用阶段(application),几何阶段(geometry),光栅化阶段(rasterizer)


应用阶段

由我们的应用主导,由cpu负责,开发者具有绝对控制权

首先,准备好场景数据(摄像机的位置,视锥体,模型,光源)。

其次,做粗细度剔除(culling)来提高渲染性能

最后,设置渲染状态(材质,纹理,shader)

这一阶段输出的是渲染图元(rendering primitives),包括点,线,三角面。


几何阶段

几何阶段负责处理渲染图元,进行逐顶点,逐多边形操作。

这一阶段将顶点坐标变换到屏幕空间,交给光栅器处理。

这一阶段输出的是屏幕空间的二维坐标,每个顶点的深度值,着色相关信息。

光栅化阶段

在GPU上运行,使用上阶段的数据将最终图形渲染到屏幕。

这一阶段决定每个渲染图元的哪些像素应该被绘制在屏幕上

对上阶段的逐顶点数据进行插值,然后进行逐像素处理。


2.2 CPU和GPU的通信

渲染流水线的起点是CPU,即应用阶段。应用阶段大致分为3个阶段

  1. 将数据加载到显存
  2. 设置渲染状态
  3. 调用Draw Call

将数据加载到显存

所有渲染所需的数据从硬盘(HDD)加载到内存(RAM),网格和纹理等数据被加载到显存(VRAM)

设置渲染状态

渲染状态:定义了场景中的网格如何被渲染

例如使用了哪个顶点着色器(vertex shader)/片元着色器(fragment shader),光源属性,材质属性。

若没有更改渲染状态,所有网格都将使用同一种渲染状态。

调用Draw Call

Draw Call是一个命令,发起方是CPU,接收方是GPU。

此命令仅仅指向一个需要被渲染的图元列表。

当给定一个Draw Call时,GPU根据渲染状态和输入的顶点坐标进行计算,输出屏幕上漂亮的像素。

这个计算过程被称为GPU流水线。


2.3 GPU流水线

概述

几何阶段和光栅化阶段可分为更小的流水线阶段,这些阶段都由GPU实现。

几何阶段:

顶点数据 –> 顶点着色器 –> 曲面细分着色器 –> 几何着色器 –> 剪裁 –> 屏幕映射

顶点数据:由应用阶段加载到显存中,由Draw Call指定的

顶点着色器(Vertex Shader):完全可编程。实现顶点的空间变换,顶点着色。

曲面细分着色器(Tessellation Shader):可选的着色器。用于细分图元。

几何着色器(Geometry Shader):可选的着色器。用于执行逐图元着色操作,或产生更多图元。

裁剪(Clipping):可配置。将不在摄像机视野内的顶点剪裁掉,并剔除某些三角图元的面元。

屏幕映射(Screen Mapping):不可配置不可编程。将每个图元的坐标转换到屏幕坐标系。


光栅化阶段:

三角形设置 –> 三角形遍历 –> 片元着色器 –> 逐片元操作 –> 屏幕图形

三角形设置(Triangle setup),三角形遍历(Triangle Traversal):固定函数阶段。

片元着色器(Fragment shader):完全可编程。实现逐片元着色操作。

逐片元操作(Per-Fragment Operation):不可编程但有极高可配置性。执行例如修改颜色,深度缓冲,混合这样的重要操作。


顶点着色器

完全可编程

顶点着色器是流水线的第一个阶段,输入来自cpu(应用阶段获得的渲染图元)

处理单位是顶点,即每个顶点都会调用一次顶点着色器。

具有独立性(无法销毁或创造顶点,无法获得顶点之间的关系),便于更高效的处理顶点。

主要工作:坐标变换(对顶点的坐标进行某种变换),逐顶点光照

一个顶点着色器必须完成将顶点坐标从模型空间转换到齐次剪裁空间,得到归一化的设备坐标(Normalized Device Coordinates,NDC)。

ps:OpenGL和unity的NDC的z分量在[-1,1]之间,而DirectX则为[0,1]


剪裁

不可编程

负责将不在摄像机视野范围内的物体裁剪出计算范围。、


屏幕映射

输入是三维坐标系下的坐标(基于NDC)

任务是把每个图元的x和y坐标转换到屏幕坐标系下(二维)。

屏幕坐标系和z分量构成窗口坐标系,一起传递到光栅化阶段

这个阶段决定了这个顶点对应屏幕的哪个像素,以及距离这个像素多远。

ps:OpenGL把屏幕左下角作为最小窗口坐标值(0,0)而DirectX定义左上角为最小窗口坐标值。


从下一步开始进入了光栅化阶段,上阶段输出的信息是屏幕坐标系下的顶点位置以及额外信息(深度值,法线方向,视角方向)。

光栅化重要目标:计算每个图元覆盖了哪些像素,为这些像素计算他们的颜色


三角形设置

由上一阶段获得的三角网格的顶点,精确计算整个三角网格的表示数据。


三角形遍历

检查每个像素是否被一个三角网格覆盖,若被覆盖生成一个片元。此阶段也被称为扫描变换。

此阶段会使用三个顶点的网格数据对整个区域的像素进行插值

输出一个片元序列

ps:片元并非真正意义上的像素,它是包含了许多状态的集合(屏幕坐标,深度信息,以及其他顶点信息等)


片元着色器

可编程

在DirectX又称像素着色器。

输入是上阶段中,对顶点着色器输出的顶点信息插值得到的数据。输出为一个或多个颜色值

纹理采样以及其他渲染技术也在这阶段完成。

片元着色器只影响单个片元,不能将任何结果发送给邻其他片元。


逐片元操作

高度可配置

在DirectX又被称为输出合并阶段

主要任务:决定每个片元的可见性(深度测试,模板测试);将通过测试的片元的颜色值和颜色缓冲区中的颜色进行合并(混合)。

ps:理解两个测试有助于了解之后的渲染队列。

片元 –> 模板测试 –> 深度测试 –> 混合 –> 颜色缓冲区

模板测试:GPU先读取(使用读取掩码)模板缓冲区中该片元 的模板值,将该值与读到的参考值进行比较。无论是否通过都能根据测试结果来修改模板缓冲区(例如在失败时模板缓冲区保持不变,通过时模板缓冲区对应位置的值+1)

深度测试:与以上读取缓冲区的值进行比较的行为类似,但深度测试的比较函数通常为小于等于(只需要显示出离屏幕最近的物体,因此深度值大缓冲区的值便直接舍弃)。若没能通过深度测试,就无法修改深度缓冲区。若通过了深度测试,就指定是否覆盖深度值。

合并:颜色缓冲区储存了上次渲染的颜色结果。对于不透明物体,可以关闭混合操作,片元着色器得到的颜色值将覆盖颜色缓冲区的像素值;对于半透明物体

,GPU会取出源颜色(片元着色器得到的颜色值)和目标颜色(颜色缓冲区的颜色值),使用一个混合函数进行混合。

Early-Z技术将深度测试提前到片元着色器之前,目的是尽早知道哪些片元被舍弃,有效提高性能。

通过以上阶段的图元将渲染到屏幕上,GPU使用双缓冲技术来确保图像是连续的

双缓冲技术:有两个缓冲区,一个用于读取一个用于写入。渲染工作在幕后进行,完成渲染时就交换当前缓冲区和幕后缓冲区,确保图像的连续性。


2.4 拓展内容

1.什么是图形API?

OpenGL和DirectX被称为图形编程接口(Graphic Application Programming Interface),在各类硬件上实现了一层抽象。

这些接口负责渲染二维或三维图形,担任了上层应用程序和底层GPU的沟通桥梁。

我们的应用运行在CPU上,应用程序通过调用OpenGL和DirectX的API将渲染所需的数据(顶点,纹理,材质)发送给显存(VRAM);同时图形API会依次向显卡驱动发送渲染命令(Draw Call)。显卡驱动知道如何和GPU等硬件通信,他们将图形API的函数翻译成GPU能理解的指令,也负责把渲染数据转换成GPU支持的格式。相当于显卡驱动就是显卡的操作系统。

2.HLSL,GLSL,Cg

GLSL:OpenGL所使用的着色语言。优点是跨平台性(Windows,Linux,Mac,移动平台),没有提供着色器编译器,因此GLSL的编译结果依赖硬件供应商。

HLSL(High Level Shading Language):DirectX所使用的的着色语言。由微软控制着色器的编译,就算是不同硬件编译结果也是相同的。平台比较有限(Windows,微软游戏机)

Cg:NVIDIA所使用的的着色语言。会根据平台不同翻译成相关的中间语言,与HLSL语法类似(可无缝移植),真正实现了跨平台。

3.有关Draw Call

Draw call的性能瓶颈在于CPU。

CPU与GPU通过使用一个命令缓冲区来实现并行流水线工作。CPU负责添加渲染命令,而GPU在完成上一次渲染任务后再从命令队列中取出新的命令。

Draw call过多会影响性能和帧率(每次Draw call都需要额外开销),提交大量很小的Draw call会造成CPU性能瓶颈。

解决方案是采用批处理(Batching):将许多小Draw call合并成一个大的Draw call,即将许多小网格合并成一个大网格,再发送给GPU在一个Draw call中同时渲染。

合并过程需要消耗时间,因此批处理更适合处理静态物体(对动态物体进行批处理需要逐帧更新,会产生额外开销)。批处理合并的网格将使用同一种渲染状态。

减少Draw call开销:避免使用大量小网格,多考虑合并小网格;避免使用过多材质,尽量共用同一个材质。

4.什么是shader

GPU流水线上高度可编程的阶段。我们依靠编写特定类型的着色器(顶点着色器,片元着色器)来控制渲染流水线的渲染细节。